//
// Define class QueryServer.
//

#include "demo/query_server.h"

#include <boost/bind.hpp>

#include <iostream>

#include "core/configure.h"
#include "core/base/auto_mutex.h"
#include "core/base/logging.h"
#include "demo/http_request.h"
#include "demo/http_response.h"
#include "demo/http_server.h"
#include "demo/mercator.h"
#include "demo/stringtok.h"

namespace demo {

const size_t QueryServer::kAddUserSize = 8;
const char QueryServer::kAddUserStr[] = "/AddUser";
const char QueryServer::kAddEventStr[] = "/AddEvent";
const char QueryServer::kUpdateUserStr[] = "/UpdateUser";
const char QueryServer::kGetUserPathPointStr[] = "/GetUserPathPoint";
const char QueryServer::kGetUserListStr[] = "/GetUserList";
const char QueryServer::kGetEventListStr[] = "/GetEventList";
const char QueryServer::kDeleteEventStr[] = "/DeleteEvent";

QueryServer::QueryServer(uint64 user_ids, uint64 event_ids)
    : user_ids_(user_ids), event_ids_(event_ids) {}

QueryServer::~QueryServer() {}

void QueryServer::OnRequest(const HttpRequest& request, HttpResponse *response) {
  if(request.GetPath() == QueryServer::kAddUserStr) {
    if(this->AddUser(request.GetPath().substr(kAddUserSize), response) == false) {
      response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
      response->SetStatusMessage("Bad Request");
      response->SetCloseConnection(true);
      response->SetBody("{\"res\":\"0\"}");
    }
  } else {
    const std::string &url = request.GetPath();
    std::string::size_type pos = url.find("?");
    if(pos == std::string::npos) {
      response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
      response->SetStatusMessage("Bad Request");
      response->SetCloseConnection(true);
      response->SetBody("<html><head><title>400 bad request</title></head>"
          "<body><h1>404 BAD REQUEST</h1>check your request and try again</body></html>");
    } else {
      std::string request_str = url.substr(0, pos);
      std::string sub_request = url.substr(pos);
      if(request_str == QueryServer::kAddEventStr) {
        if(this->AddEvent(sub_request, response) == false) {
          response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
          response->SetStatusMessage("Bad Request");
          response->SetCloseConnection(true);
          response->SetBody("{\"res\":\"0\"}");
        }
      } else if(request_str == QueryServer::kUpdateUserStr) {
        if(this->UpdateUser(sub_request, response) == false) {
          response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
          response->SetStatusMessage("Bad Request");
          response->SetCloseConnection(true);
          response->SetBody("{\"res\":\"0\"}");
        }
      } else if(request_str == QueryServer::kGetUserPathPointStr) {
        if(this->GetUserPathPoint(sub_request, response) == false) {
          response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
          response->SetStatusMessage("Bad Request");
          response->SetCloseConnection(true);
          response->SetBody("{\"res\":\"0\"}");
        }
      } else if(request_str == QueryServer::kGetUserListStr) {
        if(this->GetUserList(sub_request, response) == false) {
          response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
          response->SetStatusMessage("Bad Request");
          response->SetCloseConnection(true);
          response->SetBody("{\"res\":\"0\"}");
        }
      } else if(request_str == QueryServer::kGetEventListStr) {
        if(this->GetEventList(sub_request, response) == false) {
          response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
          response->SetStatusMessage("Bad Request");
          response->SetCloseConnection(true);
          response->SetBody("{\"res\":\"0\"}");
        }
      } else if(request_str == QueryServer::kDeleteEventStr) {
        if(this->DeleteEvent(sub_request, response) == false) {
          response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
          response->SetStatusMessage("Bad Request");
          response->SetCloseConnection(true);
          response->SetBody("{\"res\":\"0\"}");
        }
      } else {
        // std::cout << "Bad Request!!" << std::endl;
        response->SetStatusCode(HttpResponse::STATUS_400_BAD_REQUEST);
        response->SetStatusMessage("Bad Request");
        response->SetCloseConnection(true);
        response->SetBody("<html><head><title>400 bad request</title></head>"
            "<body><h1>404 BAD REQUEST</h1>check your request and try again</body></html>");
      }
    }
  }
}

bool QueryServer::AddEvent(const std::string &request, HttpResponse *response) {
  double longitude_value;
  double latitude_value;

  std::vector<std::string> vec;
  int size = StringTok(request.c_str(), "&", vec);
  if(size != 4) {
    return false;
  }

  std::string::size_type pos = vec[0].find("longitude=");
  if(pos == std::string::npos) {
    return false;
  }

  longitude_value = atof(vec[0].substr(pos).c_str());

  pos = vec[1].find("latitude=");
  if(pos == std::string::npos) {
    return false;
  }

  latitude_value = atof(vec[1].substr(pos).c_str());

  pos = vec[2].find("tag1=");
  if(pos == std::string::npos) {
    return false;
  }
  std::string tag1 = vec[2].substr(pos);

  pos = vec[3].find("tag2=");
  if(pos == std::string::npos) {
    return false;
  }
  std::string tag2 = vec[3].substr(pos);

  uint64 event = this->SignInEvent(longitude_value, latitude_value, tag1, tag2);

  response->SetStatusCode(HttpResponse::STATUS_200_OK);
  response->SetStatusMessage("OK");
  response->SetCloseConnection(true);
  response->SetContentType("text/html");
  response->AddHeader("Server", "Loogia-RubShoulders");

  char buff[64];
  snprintf(buff, sizeof(buff), "{\"res\":\"1\",\"EventID\":\"%lu\"}", event);
  response->SetBody(buff);
  return true;
}

bool QueryServer::AddUser(const std::string &request, HttpResponse *response) {
  uint64 id = SignInUser();
  response->SetStatusCode(HttpResponse::STATUS_200_OK);
  response->SetStatusMessage("OK");
  response->SetCloseConnection(true);
  response->SetContentType("text/html");
  response->AddHeader("Server", "Loogia-RubShoulders");
  char buff[64];
  snprintf(buff, sizeof(buff), "{\"res\":\"1\",\"UserID\":\"%lu\"}", id);
  response->SetBody(buff);
  return true;
}

bool QueryServer::UpdateUser(const std::string &request, HttpResponse *response) {
  double longitude_value;
  double latitude_value;
  uint64 uid;

  std::vector<std::string> vec;
  int size = StringTok(request.c_str(), "&", vec);
  if(size != 4) {
    return false;
  }

  std::string::size_type pos = vec[0].find("UID=");
  if(pos == std::string::npos) {
    return false;
  }
  pos += strlen("UID=");

  uid = atoll(vec[0].substr(pos).c_str());

  pos = vec[1].find("longitude=");
  if(pos == std::string::npos) {
    return false;
  }
  pos += strlen("longitude=");

  longitude_value = atof(vec[1].substr(pos).c_str());

  pos = vec[2].find("latitude=");
  if(pos == std::string::npos) {
    return false;
  }
  pos += strlen("latitude=");
  latitude_value = atof(vec[2].substr(pos).c_str());

  pos = vec[3].find("lTime=");
  if(pos == std::string::npos) {
    return false;
  }
  pos += strlen("lTime=");
  time_t time = atoll(vec[3].substr(pos).c_str());

  if(this->UpdateUser(uid, longitude_value, latitude_value, time) == true) {
    response->SetStatusCode(HttpResponse::STATUS_200_OK);
    response->SetStatusMessage("OK");
    response->SetCloseConnection(true);
    response->SetContentType("text/html");
    response->AddHeader("Server", "Loogia-RubShoulders");

    char buff[64];
    snprintf(buff, sizeof(buff), "{\"res\":\"1\"}");
    response->SetBody(buff);
    return true;
  }
  return false;
}

bool QueryServer::GetUserPathPoint(const std::string &request, HttpResponse *response) {
  std::string::size_type pos = request.find("UID=");
  if(pos != std::string::npos) {
    uint64 uid = atoll(request.substr(pos+strlen("UID=")).c_str());
    time_t now = time(NULL);
    AutoMutex auto_mutex(&this->mutex_user_id_);
    UserHash::iterator iterator = this->users_.find(uid);
    if(iterator != this->users_.end()) {
      UserLocationList &list = iterator->second;
      UserLocationList::iterator iter = list.begin();
      std::string res;
      res = "{\"uPointList\":[";
      char buff[64];
      for(; iter != list.end(); ++iter) {
        if(iter->time_ > now || now - iter->time_ > 3 * 24 * 60 * 60) {
          break;
        }
        snprintf(buff, sizeof(buff), "{\"x\":\"%f\",\"y\":\"%f\"},", double(iter->logitude_)/10000, double(iter->latitude_)/10000);
        res += buff;
      }
      res.erase(res.find_last_not_of(",") + 1);
      res += "]}";
      response->SetStatusCode(HttpResponse::STATUS_200_OK);
      response->SetStatusMessage("OK");
      response->SetCloseConnection(true);
      response->SetContentType("text/html");
      response->AddHeader("Server", "Loogia-RubShoulders");
      response->SetBody(res);
      return true;
    }
  }
  return false;
}

bool QueryServer::GetUserList(const std::string &request, HttpResponse *response) {
  return false;
}

bool QueryServer::GetEventList(const std::string &request, HttpResponse *response) {
  return false;
}

bool QueryServer::DeleteEvent(const std::string &request, HttpResponse *response) {
  return false;
}

uint64 QueryServer::SignInEvent(double longitude, double latitude,
    const std::string &tag, const std::string &sub_tag) {
  time_t now = time(NULL);
  double _x = 0.0, _y = 0.0;
  // Convert to coordinate.
  mercator::GeographyToCoordinate(longitude, latitude, _x, _y);

  // each point is a 50 * 50 rectangle.
  Coordinate coordinate;
  coordinate.x_ = ((int)_x) % 50;
  coordinate.y_ = ((int)_y) % 50;

  LocationEventNode event;
  event.longitude_ = longitude * 10000;
  event.latitude_ = latitude * 10000;
  event.tag_ = tag;
  event.sub_tag_ = sub_tag;

  AutoMutex auto_mutex(&this->mutex_event_id_);
  event.id_ = this->event_ids_++;

  LocationHash::iterator iterator = this->locations_.find(coordinate);
  if(iterator == this->locations_.end()) {
    LocationEventMap events;
    events.insert(std::make_pair(now, event));
    LocationUserMap users;
    this->locations_.insert(std::make_pair(coordinate, std::make_pair(events, users)));
  } else {
    iterator->second.first.insert(std::make_pair(now, event));
  }

  return event.id_;
}

uint64 QueryServer::SignInUser() {
  AutoMutex auto_mutex(&this->mutex_user_id_);
  UserLocationList node;
  this->users_.insert(std::make_pair(this->user_ids_, node));
  return this->user_ids_++;
}

bool QueryServer::UpdateUser(uint64 uid, double longitude, double latitude, time_t time) {
  double _x = 0.0, _y = 0.0;
  // Convert to coordinate.
  if(mercator::GeographyToCoordinate(mercator::DegreeToRad(longitude),
        mercator::DegreeToRad(latitude), _x, _y) == false)
    return false;

  // each point is a 50 * 50 rectangle.
  Coordinate coordinate;
  coordinate.x_ = (int)_x - ((int)_x) % 50;
  coordinate.y_ = (int)_y - ((int)_y) % 50;

  UserLocationNode user_node;
  user_node.time_ = time;
  user_node.logitude_ = longitude * 10000;
  user_node.latitude_ = latitude * 10000;

  LocationUserNode user_location;
  user_location.logitude_ = longitude * 10000;
  user_location.latitude_ = latitude * 10000;
  user_location.id_ = uid;

  {
    AutoMutex auto_mutex(&this->mutex_user_id_);

    UserHash::iterator iterator = this->users_.find(uid);
    if(iterator == this->users_.end()) {
      return false;
    }

    iterator->second.push_front(user_node);
  }
  {
    AutoMutex auto_mutex(&this->mutex_event_id_);
    LocationHash::iterator iterator = this->locations_.find(coordinate);
    if(iterator == this->locations_.end()) {
      LocationEventMap events;
      LocationUserMap users;
      users.insert(std::make_pair(time, user_location));
      this->locations_.insert(std::make_pair(coordinate, std::make_pair(events, users)));
    } else {
      iterator->second.second.insert(std::make_pair(time, user_location));
    }
  }
  return true;
}

}  // namespace demo

using namespace demo;

int main(int argc, char *argv[]) {
  InitializeLogger("./info.log", "./error.log", "./error.log");

  char server_configure[256];

  if(argc == 3) {
    if(strncmp(argv[1], "-configure", 10) == 0) {
      strncpy(server_configure, argv[2], sizeof(server_configure));
    } else {
      std::cout << "Bad argument, run it like this: ./your_binary -configure ./configure/core.xml" << std::endl;
      return 0;
    }
  } else {
    std::cout << "Bad argument, run it like this: ./your_binary -configure ./configure/core.xml" << std::endl;
    return 0;
  }

  Configure::Init(server_configure);
  if(Configure::CheckFinishLoad() == false) {
    std::cout << "load configure error!!" << std::endl;
    return 0;
  }

  HttpServer http_server(core::InetAddress("10.10.10.200", 8080), "QueryServer");
  http_server.Initialize();
  QueryServer server(0, 0);
  http_server.SetHttpCallback(boost::bind(&QueryServer::OnRequest, &server, _1, _2));
  http_server.Start();

  while(true) sleep(10000);
}

